﻿' ******************************************************************************
' ** 
' **  Yahoo! Managed
' **  Written by Marius Häusler 2011
' **  It would be pleasant, if you contact me when you are using this code.
' **  Contact: YahooFinanceManaged@gmail.com
' **  Project Home: http://code.google.com/p/yahoo-finance-managed/
' **  
' ******************************************************************************
' **  
' **  Copyright 2011 Marius Häusler
' **  
' **  Licensed under the Apache License, Version 2.0 (the "License");
' **  you may not use this file except in compliance with the License.
' **  You may obtain a copy of the License at
' **  
' **    http://www.apache.org/licenses/LICENSE-2.0
' **  
' **  Unless required by applicable law or agreed to in writing, software
' **  distributed under the License is distributed on an "AS IS" BASIS,
' **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' **  See the License for the specific language governing permissions and
' **  limitations under the License.
' ** 
' ******************************************************************************


Namespace YahooManaged.Search.BOSS.API


    Public Class SearchDownload
        Inherits Base.Download

        Public Event AsyncDownloadCompleted(ByVal sender As Base.Download, ByVal e As SearchDownloadCompletedEventArgs)

        Friend mSearchHelper As New SearchHelper

        Private mClientOptions As New SearchClientOptions
        Private mXmlParser As New XMLParser

        Public Property UniversalOptions As SearchClientOptions
            Get
                Return mClientOptions
            End Get
            Set(ByVal value As SearchClientOptions)
                mClientOptions = value
            End Set
        End Property

        Public Function Download(ByVal service As SearchOptions) As SearchResponse
            Return Me.Download(New SearchOptions() {service})
        End Function
        Public Function Download(ByVal services As IEnumerable(Of SearchOptions)) As SearchResponse
            Dim url As String = mSearchHelper.BossV2SearchUrl(mClientOptions, New List(Of SearchOptions)(services).ToArray)
            Dim resp = MyBase.DownloadStream(url, {Me.GetOAuthHeader(url)})
            Return Me.ToResponse(resp)
        End Function

        Public Sub DownloadAsync(ByVal service As SearchOptions, Optional ByVal userArgs As Object = Nothing)
            Me.DownloadAsync(New SearchOptions() {service}, userArgs)
        End Sub
        Public Sub DownloadAsync(ByVal services As IEnumerable(Of SearchOptions), Optional ByVal userArgs As Object = Nothing)
            Dim url As String = mSearchHelper.BossV2SearchUrl(mClientOptions, New List(Of SearchOptions)(services).ToArray)
            Dim lst As New List(Of SearchOptions)
            For Each serv In services
                If TypeOf serv Is WebSearchOptions Then : lst.Add(New WebSearchOptions(CType(serv, WebSearchOptions)))
                ElseIf TypeOf serv Is ImageSearchOptions Then : lst.Add(New ImageSearchOptions(CType(serv, ImageSearchOptions)))
                ElseIf TypeOf serv Is NewsSearchOptions Then : lst.Add(New NewsSearchOptions(CType(serv, NewsSearchOptions)))
                ElseIf TypeOf serv Is SpellingSearchOptions Then : lst.Add(New SpellingSearchOptions(CType(serv, SpellingSearchOptions)))
                End If
            Next
            Dim args As New AsyncDownloadArgs(userArgs) With {.Services = lst.ToArray, .ClientOptions = New SearchClientOptions(mClientOptions)}
            MyBase.DownloadStreamAsync(url, args, {Me.GetOAuthHeader(url)})
        End Sub

        Private Function GetOAuthHeader(ByVal url As String) As KeyValuePair(Of HttpRequestHeader, String)
            Dim oa As New BOSS.OAuthBase
            Dim nUrl As String = String.Empty
            Dim nParam As String = String.Empty
            Dim nonce As String = oa.GenerateNonce
            Dim timestamp As String = oa.GenerateTimeStamp
            Dim signature = oa.GenerateSignature(New Uri(Uri.EscapeUriString(url)), mClientOptions.ConsumerKey, mClientOptions.ConsumerSecret, "", "", "GET", timestamp, nonce, BOSS.OAuthBase.SignatureTypes.HMACSHA1, nUrl, nParam)
            Dim headerValue As String = String.Format("OAuth oauth_version=""1.0"", oauth_nonce=""{0}"", oauth_timestamp=""{1}"", oauth_consumer_key=""{2}"", oauth_signature_method=""HMAC-SHA1"", oauth_signature=""{3}""", nonce, timestamp, mClientOptions.ConsumerKey, signature)
            Return New KeyValuePair(Of HttpRequestHeader, String)(HttpRequestHeader.Authorization, headerValue)
        End Function

        Private Sub AsyncDownload_Completed(ByVal sender As Base.Download, ByVal e As Base.StreamDownloadCompletedEventArgs) Handles MyBase.AsyncStreamDownloadCompleted
            Dim args As AsyncDownloadArgs = CType(e.UserArgs, AsyncDownloadArgs)
            RaiseEvent AsyncDownloadCompleted(Me, New SearchDownloadCompletedEventArgs(args.UserArgs, Me.ToResponse(e.Response), args.ClientOptions, args.Services))
        End Sub

        Private Function ToResponse(ByVal resp As Base.StreamResponse) As SearchResponse
            Dim containers As New List(Of BossSearchResultContainer)
            Dim lst As New List(Of SearchResult)

            Dim xmlDoc = mHelper.GetXmlDocument(resp.Result)
            If xmlDoc IsNot Nothing Then
                Dim bossResponseNode = xmlDoc.LastChild
                If bossResponseNode.Name = "bossresponse" Then
                    Dim respCode = CInt(bossResponseNode.Attributes("responsecode").Value)
                    If respCode = 200 Then

                        For Each containerNode As XmlNode In bossResponseNode.ChildNodes
                            Dim results As New List(Of SearchResult)
                            Dim start As Integer = CInt(containerNode.Attributes("start").Value)
                            Dim count As Integer = CInt(containerNode.Attributes("count").Value)
                            Dim totalResults As Long = CLng(containerNode.Attributes("totalresults").Value)

                            Dim resultsNode As XmlNode = containerNode.FirstChild
                            If resultsNode.Name = "results" Then
                                For Each resultNode As XmlNode In resultsNode.ChildNodes
                                    If resultNode.Name = "result" Then
                                        Dim res As SearchResult = Nothing
                                        Select Case containerNode.Name
                                            Case "web", "limitedweb"
                                                res = Me.ToBossWebSearchResult(resultNode)
                                            Case "images"
                                                res = Me.ToBossImageSearchResult(resultNode)
                                            Case "news"
                                                res = Me.ToBossNewsSearchResult(resultNode)
                                            Case "spelling"
                                                res = Me.ToBossSpellingSearchResult(resultNode)
                                        End Select
                                        If res IsNot Nothing Then results.Add(res)
                                    End If
                                Next
                            End If

                            Select Case containerNode.Name
                                Case "web", "limitedweb"
                                    Dim mngResults As New List(Of WebSearchResult)
                                    For Each res In results
                                        If TypeOf res Is WebSearchResult Then
                                            mngResults.Add(CType(res, WebSearchResult))
                                        End If
                                    Next
                                    containers.Add(New WebSearchResultContainer(mngResults.ToArray, BossResultType.Web, start, count, totalResults))
                                Case "images"
                                    Dim mngResults As New List(Of ImageSearchResult)
                                    For Each res In results
                                        If TypeOf res Is ImageSearchResult Then
                                            mngResults.Add(CType(res, ImageSearchResult))
                                        End If
                                    Next
                                    containers.Add(New ImageSearchResultContainer(mngResults.ToArray, BossResultType.Images, start, count, totalResults))
                                Case "news"
                                    Dim mngResults As New List(Of NewsSearchResult)
                                    For Each res In results
                                        If TypeOf res Is NewsSearchResult Then
                                            mngResults.Add(CType(res, NewsSearchResult))
                                        End If
                                    Next
                                    containers.Add(New NewsSearchResultContainer(mngResults.ToArray, BossResultType.News, start, count, totalResults))
                                Case "spelling"
                                    Dim mngResults As New List(Of SpellingSearchResult)
                                    For Each res In results
                                        If TypeOf res Is SpellingSearchResult Then
                                            mngResults.Add(CType(res, SpellingSearchResult))
                                        End If
                                    Next
                                    containers.Add(New SpellingSearchResultContainer(mngResults.ToArray, BossResultType.Spelling, start, count, totalResults))
                            End Select

                        Next
                    End If
                End If
            End If


            Return New SearchResponse(resp.Connection, containers.ToArray)
        End Function




        Private Function ToBossWebSearchResult(ByVal node As XmlNode) As BOSS.WebSearchResult
            Dim result As SearchResult = mXmlParser.ToSearchResult(node)
            If result IsNot Nothing Then
                Dim dispUrl As String = String.Empty
                Dim crwDate As Date
                Dim language As Language = YahooManaged.Language.en
                Dim smFeed As String = String.Empty

                For Each prpNode As XmlNode In node.ChildNodes
                    Select Case prpNode.Name
                        Case "dispurl" : dispUrl = prpNode.InnerText
                        Case "date" : Date.TryParse(prpNode.InnerText, New Globalization.CultureInfo("en-US"), Globalization.DateTimeStyles.AssumeUniversal, crwDate)
                        Case "language" : language = Me.StringToLanguage(prpNode.InnerText)
                        Case "smfeed" : smFeed = prpNode.InnerXml
                    End Select
                Next
                Return New BOSS.WebSearchResult(result, dispUrl, crwDate, language, smFeed)
            Else
                Return Nothing
            End If
        End Function

        Private Function ToBossImageSearchResult(ByVal node As XmlNode) As BOSS.ImageSearchResult

            Dim result As SearchResult = mXmlParser.ToSearchResult(node)
            If result IsNot Nothing Then
                Dim refererUrl As Uri = Nothing
                Dim refererClickUrl As Uri = Nothing
                Dim tmbUrl As Uri = Nothing
                Dim fileFormat As ImageFileType
                Dim size As Long
                Dim height, width, tmbHeight, tmbWidth As Integer
                Dim convCult As New Globalization.CultureInfo("en-US")

                For Each prpNode As XmlNode In node.ChildNodes

                    If prpNode.Name.Contains("thumbnail") Then
                        Debug.WriteLine(prpNode.Name)
                    End If

                    Select Case prpNode.Name
                        Case "refererurl" : refererUrl = New Uri(prpNode.InnerText)
                        Case "refererclickurl" : refererClickUrl = New Uri(prpNode.InnerText)
                        Case "size"
                            Dim srcSize As Double = 0
                            If prpNode.InnerText.EndsWith("Bytes") Then
                                Double.TryParse(prpNode.InnerText.Replace("Bytes", ""), Globalization.NumberStyles.Any, convCult, srcSize)
                            ElseIf prpNode.InnerText.EndsWith("KB") Then
                                Double.TryParse(prpNode.InnerText.Replace("KB", ""), Globalization.NumberStyles.Any, convCult, srcSize)
                                srcSize *= 1024
                            ElseIf prpNode.InnerText.EndsWith("MB") Then
                                Double.TryParse(prpNode.InnerText.Replace("MB", ""), Globalization.NumberStyles.Any, convCult, srcSize)
                                srcSize *= 1024 ^ 2
                            End If
                            size = CLng(srcSize)
                        Case "format"
                            Select Case prpNode.InnerText.ToLower
                                Case "bmp" : fileFormat = ImageFileType.Bmp
                                Case "gif" : fileFormat = ImageFileType.Gif
                                Case "jpg" : fileFormat = ImageFileType.Jpeg
                                Case "jpeg" : fileFormat = ImageFileType.Jpeg
                                Case "png" : fileFormat = ImageFileType.Png
                                Case Else : fileFormat = ImageFileType.Any
                            End Select
                        Case "height" : Integer.TryParse(prpNode.InnerText, height)
                        Case "width" : Integer.TryParse(prpNode.InnerText, width)
                        Case "thumbnailurl" : If prpNode.InnerText <> String.Empty Then tmbUrl = New Uri(prpNode.InnerText)
                        Case "thumbnailwidth" : Integer.TryParse(prpNode.InnerText, tmbWidth)
                        Case "thumbnailheight" : Integer.TryParse(prpNode.InnerText, tmbHeight)
                    End Select
                Next

                Return New BOSS.ImageSearchResult(result, refererUrl, refererClickUrl, size, fileFormat, height, width, tmbUrl, tmbHeight, tmbWidth)
            Else
                Return Nothing
            End If
        End Function

        Public Function ToBossNewsSearchResult(ByVal node As XmlNode) As BOSS.NewsSearchResult
            Dim result As SearchResult = mXmlParser.ToSearchResult(node)
            If result IsNot Nothing Then
                Dim crwDate As DateTime
                Dim language As YahooManaged.Language = YahooManaged.Language.en
                Dim source As String = String.Empty
                Dim sourceUrl As Uri = Nothing

                For Each prpNode As XmlNode In node.ChildNodes
                    Select Case prpNode.Name
                        Case "date" : crwDate = New DateTime(1970, 1, 1).AddSeconds(CInt(prpNode.InnerText))
                        Case "language" : language = Me.StringToLanguage(prpNode.InnerText)
                        Case "source" : source = prpNode.InnerText
                        Case "sourceurl" : If prpNode.InnerText.Trim <> String.Empty Then sourceUrl = New Uri(prpNode.InnerText, UriKind.Absolute)
                    End Select
                Next

                Return New BOSS.NewsSearchResult(result, source, sourceUrl, crwDate, language)
            Else
                Return Nothing
            End If
        End Function

        Public Function ToBossSpellingSearchResult(ByVal node As XmlNode) As BOSS.SpellingSearchResult
            If node IsNot Nothing AndAlso node.Name.ToLower = "result" Then
                Dim suggestion As String = String.Empty

                For Each prpNode As XmlNode In node.ChildNodes
                    If prpNode.Name = "suggestion" Then
                        suggestion = prpNode.InnerText
                    End If
                Next
                Return New BOSS.SpellingSearchResult(suggestion)
            Else
                Return Nothing
            End If
        End Function

        Private Function StringToLanguage(ByVal l As String) As YahooManaged.Language
            If l.Length >= 2 Then
                Dim langCode As String = l.Substring(0, 2)
                For Each lang As YahooManaged.Language In [Enum].GetValues(GetType(YahooManaged.Language))
                    If lang.ToString = langCode Then
                        Return lang
                    End If
                Next
            End If
            Return YahooManaged.Language.en
        End Function


        Private Class AsyncDownloadArgs
            Inherits Base.DownloadEventArgs

            Public ClientOptions As SearchClientOptions
            Public Services() As SearchOptions

            Public Sub New(ByVal userArgs As Object)
                MyBase.New(userArgs)
            End Sub

        End Class

    End Class



    Public Class SearchResponse
        Inherits Base.Response

        Public Overloads ReadOnly Property Result() As BossSearchResultContainer()
            Get
                Return TryCast(MyBase.Result, BossSearchResultContainer())
            End Get
        End Property

        Friend Sub New(ByVal info As Base.ConnectionInfo, ByVal result As BossSearchResultContainer())
            MyBase.New(info, result)
        End Sub
    End Class


    Public Class SearchDownloadCompletedEventArgs
        Inherits Base.DownloadCompletedEventArgs

        Private mClientOptions As SearchClientOptions
        Private mServices() As SearchOptions

        Public ReadOnly Property ClientOptions As SearchClientOptions
            Get
                Return mClientOptions
            End Get
        End Property
        Public ReadOnly Property Services As SearchOptions()
            Get
                Return mServices
            End Get
        End Property

        Public Overloads ReadOnly Property Response() As SearchResponse
            Get
                Return DirectCast(MyBase.Response, SearchResponse)
            End Get
        End Property
        Public Sub New(ByVal userArgs As Object, ByVal resp As SearchResponse, ByVal clientOptions As SearchClientOptions, ByVal services() As SearchOptions)
            MyBase.New(userArgs, resp)
            mClientOptions = clientOptions
            mServices = services
        End Sub

    End Class


End Namespace
